#define _USE_MATH_DEFINES
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include "SampleInterfaces.h"
#include "Recast.h"
#include "RecastDebugDraw.h"
#include "DetourDebugDraw.h"
#include "PerfTimer.h"
#include "SDL.h"
#include "SDL_opengl.h"

#ifdef WIN32
#    define snprintf _snprintf
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////

BuildContext::BuildContext() :
    m_messageCount(0),
    m_textPoolSize(0)
{
    resetTimers();
}

BuildContext::~BuildContext()
{
}

// Virtual functions for custom implementations.
void BuildContext::doResetLog()
{
    m_messageCount = 0;
    m_textPoolSize = 0;
}

void BuildContext::doLog(const rcLogCategory category, const char* msg, const int len)
{
    if (!len) return;
    if (m_messageCount >= MAX_MESSAGES)
        return;
    char* dst = &m_textPool[m_textPoolSize];
    int n = TEXT_POOL_SIZE - m_textPoolSize;
    if (n < 2)
        return;
    char* cat = dst;
    char* text = dst+1;
    const int maxtext = n-1;
    // Store category
    *cat = (char)category;
    // Store message
    const int count = rcMin(len+1, maxtext);
    memcpy(text, msg, count);
    text[count-1] = '\0';
    m_textPoolSize += 1 + count;
    m_messages[m_messageCount++] = dst;
}

void BuildContext::doResetTimers()
{
    for (int i = 0; i < RC_MAX_TIMERS; ++i)
        m_accTime[i] = -1;
}

void BuildContext::doStartTimer(const rcTimerLabel label)
{
    m_startTime[label] = getPerfTime();
}

void BuildContext::doStopTimer(const rcTimerLabel label)
{
    const TimeVal endTime = getPerfTime();
    const int deltaTime = (int)(endTime - m_startTime[label]);
    if (m_accTime[label] == -1)
        m_accTime[label] = deltaTime;
    else
        m_accTime[label] += deltaTime;
}

int BuildContext::doGetAccumulatedTime(const rcTimerLabel label) const
{
    return m_accTime[label];
}

void BuildContext::dumpLog(const char* format, ...)
{
    // Print header.
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap);
    va_end(ap);
    printf("\n");
    
    // Print messages
    const int TAB_STOPS[4] = { 28, 36, 44, 52 };
    for (int i = 0; i < m_messageCount; ++i)
    {
        const char* msg = m_messages[i]+1;
        int n = 0;
        while (*msg)
        {
            if (*msg == '\t')
            {
                int count = 1;
                for (int j = 0; j < 4; ++j)
                {
                    if (n < TAB_STOPS[j])
                    {
                        count = TAB_STOPS[j] - n;
                        break;
                    }
                }
                while (--count)
                {
                    putchar(' ');
                    n++;
                }
            }
            else
            {
                putchar(*msg);
                n++;
            }
            msg++;
        }
        putchar('\n');
    }
}

int BuildContext::getLogCount() const
{
    return m_messageCount;
}

const char* BuildContext::getLogText(const int i) const
{
    return m_messages[i]+1;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void DebugDrawGL::depthMask(bool state)
{
    glDepthMask(state ? GL_TRUE : GL_FALSE);
}

void DebugDrawGL::begin(duDebugDrawPrimitives prim, float size)
{
    switch (prim)
    {
        case DU_DRAW_POINTS:
            glPointSize(size);
            glBegin(GL_POINTS);
            break;
        case DU_DRAW_LINES:
            glLineWidth(size);
            glBegin(GL_LINES);
            break;
        case DU_DRAW_TRIS:
            glBegin(GL_TRIANGLES);
            break;
        case DU_DRAW_QUADS:
            glBegin(GL_QUADS);
            break;
    };
}

void DebugDrawGL::vertex(const float* pos, unsigned int color)
{
    glColor4ubv((GLubyte*)&color);
    glVertex3fv(pos);
}

void DebugDrawGL::vertex(const float x, const float y, const float z, unsigned int color)
{
    glColor4ubv((GLubyte*)&color);
    glVertex3f(x,y,z);
}

void DebugDrawGL::end()
{
    glEnd();
    glLineWidth(1.0f);
    glPointSize(1.0f);
}

////////////////////////////////////////////////////////////////////////////////////////////////////

FileIO::FileIO() :
    m_fp(0),
    m_mode(-1)
{
}

FileIO::~FileIO()
{
    if (m_fp) fclose(m_fp);
}

bool FileIO::openForWrite(const char* path)
{
    if (m_fp) return false;
    m_fp = fopen(path, "wb");
    if (!m_fp) return false;
    m_mode = 1;
    return true;
}

bool FileIO::openForRead(const char* path)
{
    if (m_fp) return false;
    m_fp = fopen(path, "rb");
    if (!m_fp) return false;
    m_mode = 2;
    return true;
}

bool FileIO::isWriting() const
{
    return m_mode == 1;
}

bool FileIO::isReading() const
{
    return m_mode == 2;
}

bool FileIO::write(const void* ptr, const size_t size)
{
    if (!m_fp || m_mode != 1) return false;
    fwrite(ptr, size, 1, m_fp);
    return true;
}

bool FileIO::read(void* ptr, const size_t size)
{
    if (!m_fp || m_mode != 2) return false;
    fread(ptr, size, 1, m_fp);
    return true;
}


